package https

import (
	"context"
	"sync"
	"time"
)

// 分组请求结构
// 每个请求组只可运行一次
type WGroup struct {
	HasTimeOut   bool            // 是否是超时操作
	Error        []error         // 错误信息(只有在AddError以后才能判断此值，否则此值会一直为nil)
	runTime      time.Duration   // 组运行时间
	runStartTime time.Time       // 开始执行时间
	runStatus    bool            // 是否已执行run请求
	value        []func()        // 待请求的函数
	valueError   []func() error  // 可以返回错误信息的函数
	context      context.Context // 请求控制结构
}

// HTTP请求组，将一组HTTP请求放置在一起，进行并发请求，直到最后一个请求完成再往下进行处理
// 该程序的耗时与传入的具体函数有关，一般情况下比传入的程序的最大耗时再大一些
// 该方法用于实现比 sync.WaitGroup 更精细的功能，效率比 sync.WaitGroup 稍低
//
//	fun	待处理的函数请求
func Group(fun ...func()) *WGroup {
	return &WGroup{
		value:   fun,
		context: context.TODO(),
	}
}

// 添加可以返回错误信息的函数调用
//
//	f	带有error返回值的错误信息
func (g *WGroup) AddError(f func() error) *WGroup {
	g.valueError = append(g.valueError, f)
	return g
}

// 添加异步待处理请求
//
//	fun	待处理的函数请求
func (g *WGroup) Add(fun ...func()) *WGroup {
	g.value = append(g.value, fun...)
	return g
}

// 开始执行
func (g *WGroup) Run() *WGroup {
	if g.runStatus {
		return g
	}
	g.runStatus = true
	g.runStartTime = time.Now()

	// 并发执行
	var wg sync.WaitGroup
	// 设置并发执行的函数数量
	wg.Add(len(g.value) + len(g.valueError))
	for i := range g.value {
		go func(t int) {
			defer wg.Done()
			// 函数执行
			g.value[t]()
		}(i)
	}
	for i := range g.valueError {
		go func(t int) {
			defer wg.Done()
			// 函数执行
			err := g.valueError[t]()
			if err != nil {
				g.Error = append(g.Error, err)
			}
		}(i)
	}
	// 为了实现整体超时，而不需要单独等每个并发执行完成
	// 所以此处设置一个通道，等待所有并发执行完成
	done := make(chan struct{})
	// 启动一个协程，等待所有并发执行完成
	go func() {
		wg.Wait()
		close(done)
	}()
	// 超时控制，如果整体超时则设置 HasTimeOut 为 true
	// 若未整体超时，则正常等待所有并发执行完成（使用通道 done 来实现控制）
	select {
	case <-g.context.Done():
		g.HasTimeOut = true
	case <-done:
	}
	// 计算运行时间
	g.runTime = time.Since(g.runStartTime)
	return g
}

// 获取请求组运行时间
func (g *WGroup) Times() time.Duration {
	if !g.runStatus {
		g.Run()
	}
	return g.runTime
}

// 设置中控context结构
//
//	ctx	中控结构
func (g *WGroup) WithContext(ctx context.Context) *WGroup {
	g.context = ctx
	return g
}
